﻿using System;
using System.Collections.Generic;
using API;
using AVFoundation;
using CoreFoundation;
using Foundation;
using MobileCoreServices;
using MyExtensions;
using ObjCRuntime;

namespace mb.iOS
{
    public abstract class MyResourceLoader : AVAssetResourceLoaderDelegate
    {
        public virtual void Close() { }

    }

    public class MyResourceLoaderNS : MyResourceLoader, INSUrlSessionDataDelegate
    {
        static ILog logger = mb.ServiceLocator.Instance.Get<ILog>();
        NSUrlSession session;
        AVUrlAsset loadingAsset;
        string realUrl;
        AVAssetResourceLoadingRequest infoRequest;
        byte[] keyBuffer;

        public Dictionary<NSUrlSessionDataTask, AVAssetResourceLoadingRequest> mapTask = new Dictionary<NSUrlSessionDataTask, AVAssetResourceLoadingRequest>();

        public delegate void OnRequestChanged();
        public OnRequestChanged onRequestChanged;

        public MyResourceLoaderNS(AVUrlAsset asset, string key)
        {
            IStartOption startOption = mb.ServiceLocator.Instance.Get<IStartOption>();
            var raw = startOption.GetBoolValue("movie-raw");
            if (!raw)
                keyBuffer = System.Text.Encoding.ASCII.GetBytes(key);

            loadingAsset = asset;
            asset.ResourceLoader.SetDelegate(this, DispatchQueue.GetGlobalQueue((DispatchQueuePriority) 0x21));

            NSUrlSessionConfiguration configure = NSUrlSessionConfiguration.DefaultSessionConfiguration;
            session = NSUrlSession.FromConfiguration(configure, this, null);

            var url = loadingAsset.Url.ToString();
            realUrl = url.Replace("jj", "http");
            //logger.Log("media create  thread", url, System.Threading.Thread.CurrentThread.ManagedThreadId, raw);

        }
        public override void Close()
        {

        }

        void PushRequest(AVAssetResourceLoadingRequest req)
        {

            long offset = req.DataRequest.CurrentOffset;
            long length = req.DataRequest.RequestedLength - (offset - req.DataRequest.RequestedOffset);
            bool isToEnd = false;
            //if (@available(iOS 9, *))
            {
                // isToEnd = req.DataRequest.RequestsAllDataToEndOfResource;
            }

            string byteRange = isToEnd ? string.Format("bytes={0}-", offset) : string.Format("bytes={0}-{1}", offset, offset + length);

            NSMutableUrlRequest urlRequest = req.Request.MutableCopy() as NSMutableUrlRequest;
            NSMutableDictionary headers = new NSMutableDictionary(urlRequest.Headers);
            headers.Add(NSObject.FromObject("range"), NSObject.FromObject(byteRange));

            urlRequest.Url = NSUrlComponents.FromString(realUrl).Url;
            urlRequest.Headers = headers;

            NSUrlSessionDataTask task = session.CreateDataTask(urlRequest);
            mapTask.Add(task, req);
            // Console.WriteLine("create task[{0}] from req[{1}], range:{2}-{3}, {4}", task.GetHashCode(), req.GetHashCode(), offset, offset + length, byteRange);
            task.Resume();

            onRequestChanged?.Invoke();
        }

        public override void DidCancelLoadingRequest(AVAssetResourceLoader resourceLoader, AVAssetResourceLoadingRequest loadingRequest)
        {

            var dreq = loadingRequest.DataRequest;

            NSUrlSessionDataTask task = null;
            foreach (var it in mapTask)
            {
                if (it.Value == loadingRequest)
                {
                    task = it.Key;
                    break;
                }
            }
            if (task == null) return;
            // Console.WriteLine("cancel task: {0}, recv:{1}", task.GetHashCode(), dreq.CurrentOffset - dreq.RequestedOffset);
            //logger.Log("cancel request", loadingRequest.GetHashCode(), "already get", dreq.CurrentOffset - dreq.RequestedOffset);
            if (loadingRequest == infoRequest) { logger.Log("request info finish1"); }
            task.Cancel();
            mapTask.Remove(task);
            onRequestChanged?.Invoke();
        }

        public override bool ShouldWaitForLoadingOfRequestedResource(AVAssetResourceLoader resourceLoader, AVAssetResourceLoadingRequest loadingRequest)
        {
            var dreq = loadingRequest.DataRequest;
            var ireq = loadingRequest.ContentInformationRequest;
            var tid = System.Threading.Thread.CurrentThread.ManagedThreadId;
            //logger.Log($"{tid}/ShouldWaitForLoadingOfRequestedResource");

            if (ireq != null)
            {
                //logger.Log("info request", loadingRequest.GetHashCode(), ":", dreq.RequestedOffset, dreq.RequestedLength);
                infoRequest = loadingRequest;
            }

            if (dreq != null)
            {
                //logger.Log("data request", loadingRequest.GetHashCode(), ":", dreq.RequestedOffset, dreq.RequestedLength);
                // logger.Log("media read  thread", System.Threading.Thread.CurrentThread.ManagedThreadId);
                PushRequest(loadingRequest);
            }

            return true;
        }

        [Export("URLSession:dataTask:didReceiveData:")]
        public void DidReceiveData(NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data)
        {
            // Console.WriteLine("DidReceiveData task{0}, data len:{1}", dataTask.GetHashCode(), data.Length);
            AVAssetResourceLoadingRequest req = null;
            bool has = mapTask.TryGetValue(dataTask, out req);
            if (!has || req == null) return;
            if (req.DataRequest == null) return;

            if (req.DataRequest.CurrentOffset == req.DataRequest.RequestedOffset)
            {
                // logger.Log("DidReceiveData", req.GetHashCode());
            }

            if (keyBuffer != null)
            {
                var buffer = data.ToArray();
                long koff = req.DataRequest.CurrentOffset;
                for (int i = 0; i < (int) data.Length; i++, koff++)
                {
                    buffer[i] = (byte) (buffer[i] ^ keyBuffer[koff % keyBuffer.Length]);
                }

                data = NSMutableData.FromArray(buffer);
            }

            var tid = System.Threading.Thread.CurrentThread.ManagedThreadId;
            //logger.Log($"{tid}/data recieved", req.GetHashCode(), req.DataRequest.CurrentOffset, data.Length);

            req.DataRequest.Respond(data);
            if (req.DataRequest.CurrentOffset - req.DataRequest.RequestedOffset >= req.DataRequest.RequestedLength)
            {
                req.FinishLoading();
                mapTask.Remove(dataTask);
                //logger.Log("finish request", req.GetHashCode());
            }
            onRequestChanged?.Invoke();
        }

        [Export("URLSession:dataTask:didReceiveResponse:completionHandler:")]
        public void DidReceiveResponse(NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action<NSUrlSessionResponseDisposition> completionHandler)
        {
            var tid = System.Threading.Thread.CurrentThread.ManagedThreadId;
            if (this.infoRequest != null)
            {
                var infoRequest = this.infoRequest;
                var headers = (response as NSHttpUrlResponse).AllHeaderFields;
                var mt = response.MimeType;
                var rangeInfo = headers.ValueForKey(new NSString("Content-Range"))?.ToString().Split('/');
                infoRequest.ContentInformationRequest.ContentType = mt;
                infoRequest.ContentInformationRequest.ContentLength = int.Parse(rangeInfo[1]);
                infoRequest.ContentInformationRequest.ByteRangeAccessSupported = true;
                infoRequest.FinishLoading();
                this.infoRequest = null;
                mapTask.Remove(dataTask);
                onRequestChanged?.Invoke();
                completionHandler?.Invoke(NSUrlSessionResponseDisposition.Cancel);
                //logger.Log($"{tid}/finish info request", infoRequest.GetHashCode(), headers.ToString(), rangeInfo[1]);
                return;
            }

            completionHandler?.Invoke(NSUrlSessionResponseDisposition.Allow);

        }

        public void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
        {
            //Console.WriteLine("DidComplete");
            //if (error == null)
            //    Console.WriteLine("Task: {0} completed successfully", task);
            //else
            //Console.WriteLine("Task: {0} completed with error: {1}", task, error.LocalizedDescription);

        }

    }
}